/*
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 *  Copyright (C) 2006-2008 XNeur Team
 *
 */

#ifdef HAVE_CONFIG_H
#  include "config.h"
#endif

#include <X11/keysym.h>
#include <X11/XKBlib.h>

#include <stdlib.h>
#include <strings.h>
#include <string.h>
#include <stdio.h>
#include <signal.h>
#include <pthread.h>
#include <ctype.h>

#include "xnconfig.h"
#include "xnconfig_files.h"

#include "xdefines.h"
#include "xstring.h"
#include "xfocus.h"
#include "xswitchlang.h"
#include "xselection.h"
#include "xbtable.h"
#include "xevent.h"
#include "xwindow.h"
#include "xkeymap.h"
#include "xutils.h"

#include "types.h"
#include "list_char.h"
#include "log.h"
#include "text.h"
#include "xprogram.h"

#define KLB_NO_ACTION           0	// Modifier, function etc
#define KLB_ADD_SYM             1	// Alpha
#define KLB_DEL_SYM             2	// Backspace
#define KLB_SPACE               3	// Word end (space etc)
#define KLB_ENTER               4	// Enter
#define KLB_CLEAR               5	// Home, End etc

#define MANUAL_FLAG_UNSET	0
#define MANUAL_FLAG_SET		1
#define MANUAL_FLAG_NEED_FLUSH	2

#define NO_MODIFIER_MASK	0

extern struct _xkeylog_config *xconfig;

struct _xwindow *main_window;

// Private
static int get_auto_action(KeySym key, int modifier_mask)
{
	// Null symbol
	if (key == 0)
		return KLB_NO_ACTION;

	// Cursor keys
	if (IsCursorKey(key))
		return KLB_CLEAR;

	// KeyPad keys
	if (IsKeypadKey(key))
	{
		if (get_key_state(XK_Num_Lock) != 0)
		{
			if (modifier_mask & ControlMask || modifier_mask & Mod1Mask || modifier_mask & ShiftMask || modifier_mask & Mod4Mask)
				return KLB_CLEAR;
			return KLB_ADD_SYM;
		}

		switch (key)
		{
			case XK_KP_Divide:
			case XK_KP_Multiply:
			case XK_KP_Add:
			case XK_KP_Subtract:
				return KLB_ADD_SYM;
		}

		return KLB_CLEAR;
	}

	// Func, Mod, PF, PrivateKeypad keys
	if (IsFunctionKey(key) || IsModifierKey(key) || IsPFKey(key) || IsPrivateKeypadKey(key))
		return KLB_NO_ACTION;

	// MiscFunc keys
	if (IsMiscFunctionKey(key))
	{
		if (key == XK_Insert)
		{
			if (modifier_mask & ControlMask || modifier_mask & Mod1Mask || modifier_mask & ShiftMask || modifier_mask & Mod4Mask)
				return KLB_CLEAR;
		}
		return KLB_NO_ACTION;
	}

	// Del, bkspace, tab, return, alpha & num keys
	switch (key)
	{
		case XK_BackSpace:
			return KLB_DEL_SYM;
		case XK_Pause:
		case XK_Escape:
		case XK_Sys_Req:
		case XK_Delete:
			return KLB_NO_ACTION;
		case XK_Return:
		case XK_Tab:
			return KLB_ENTER;
		case XK_space:
		case XK_equal:
		case XK_plus:
		case XK_minus:
		case XK_slash:
		case XK_bar:
		case XK_backslash:
		case XK_question:
			return KLB_SPACE;
	}

	if (modifier_mask & ControlMask || modifier_mask & Mod1Mask || modifier_mask & Mod4Mask)
		return KLB_NO_ACTION;

	return KLB_ADD_SYM;
}

static void xprogram_update(struct _xprogram *p)
{
	p->last_window = p->focus->owner_window;

	int status = p->focus->get_focus_status(p->focus, &p->app_forced_mode, &p->app_focus_mode);
	p->event->set_owner_window(p->event, p->focus->owner_window);
	
	if (status == FOCUS_UNCHANGED)
		return;

	p->string->save_and_clear(p->string, p->last_window);
		
	if (status == FOCUS_NONE)
		return;

	int listen_mode = LISTEN_GRAB_INPUT;
	if (p->app_focus_mode == FOCUS_EXCLUDED)
		listen_mode = LISTEN_DONTGRAB_INPUT;

	p->focus->update_events(p->focus, listen_mode);
}

static void xprogram_process_input(struct _xprogram *p)
{
	p->update(p);

	while (1)
	{
		int type = p->event->get_next_event(p->event);

		if (p->event->event.xany.window == main_window->flag_window)
			continue;

		switch (type)
		{
			case ClientMessage:
			{
				// Exit from main cycle by message to main window
				XClientMessageEvent *cme = (XClientMessageEvent *) &(p->event->event);
				if (cme->message_type == main_window->close_atom)
				{
					log_message(LOG, _("Exitting from main cycle"));
					return;
				}
				break;
			}
			case KeyPress:
			{
				log_message(TRACE, _("Received KeyPress (event type %d)"), type);
				// Save received event
				p->event->default_event = p->event->event;
				// Processing received event 
				p->on_key_action(p);
				// Restore event
				if (p->event->default_event.xkey.keycode != 0)
				{
					p->event->event = p->event->default_event;
					p->event->send_next_event(p->event);		
				}
				break;
			}
			case KeyRelease:
			{
				log_message(TRACE, _("Received KeyRelease (event type %d)"), type);
				// Resend special key back to window
				if (p->event->default_event.xkey.keycode != 0)
					p->event->send_next_event(p->event);
				break;
			}
			case FocusIn:
			case LeaveNotify:
			case EnterNotify:
			{
				if (type == FocusIn)
				{
					log_message(TRACE, _("Received FocusIn (event type %d)"), type);
					
					p->last_layout = get_active_keyboard_group();

					p->update(p);
				}
				//else if (type == LeaveNotify)
				//	log_message(TRACE, _("Received LeaveNotify (event type %d)"), type);
				//else if (type == EnterNotify)
				//	log_message(TRACE, _("Received EnterNotify (event type %d)"), type);

				break;
			}
			case FocusOut:
			{
				log_message(TRACE, _("Received FocusOut (event type %d)"), type);

				p->last_layout = get_active_keyboard_group();
				p->update(p);
				break;
			}
			case SelectionNotify:
			{
				log_message(TRACE, _("Received SelectionNotify (event type %d)"), type);
				p->process_selection_notify(p);
				break;
			}
			case SelectionRequest:
			{
				log_message(TRACE, _("Received SelectionRequest (event type %d)"), type);
				p->process_selection_notify(p);
				break;
			}
			case ButtonPress:
			{
				p->string->save_and_clear(p->string, p->focus->owner_window);
				log_message(TRACE, _("Received ButtonPress on window %d  (event type %d)"), p->event->event.xbutton.window, type);

				// Unfreeze and resend grabbed event
				XAllowEvents(main_window->display, ReplayPointer, CurrentTime);
				break;
			}
			case PropertyNotify:
			{
				if (XInternAtom(main_window->display, "XKLAVIER_STATE", FALSE) == p->event->event.xproperty.atom)
				{
					log_message(TRACE, _("Received Property Notify (layout switch event) (event type %d)"), type);

					// Flush string
					//p->string->clear(p->string);
				}
				break;
			}
			default:
			{
				log_message(DEBUG, _("Uncatched event with type %d)"), type);
				break;
			}
		}
	}
}

static void xprogram_process_selection_notify(struct _xprogram *p)
{
	char *event_text = get_selected_text(&p->event->event.xselection);
	if (event_text == NULL)
	{
		p->selected_mode = ACTION_NONE;
		return;
	}

	char *selected_text = strdup(event_text);
	XFree(event_text);

	p->string->set_content(p->string, selected_text);
	free(selected_text);

	p->focus->update_events(p->focus, LISTEN_DONTGRAB_INPUT);	// Disable receiving events

	// Block events of keyboard
	set_event_mask(p->focus->owner_window, None);
	grab_spec_keys(p->focus->owner_window, FALSE);

	on_selection_converted();

	if (xconfig->save_selection)
		p->event->send_selection(p->event, p->string->cur_pos);

	p->update(p);

	p->string->save_and_clear(p->string, p->focus->owner_window);
	p->selected_mode = ACTION_NONE;
}

static void xprogram_on_key_action(struct _xprogram *p)
{
	KeySym key = p->event->get_cur_keysym(p->event);

	// Delete language modifier mask
	int modifier_mask = p->event->get_cur_modifiers(p->event);
	
	int user_action = get_user_action(key, modifier_mask);
	if (user_action >= 0) 
	{
		p->perform_user_action(p, user_action);
	    p->event->default_event.xkey.keycode = 0;
		return;
	}

	enum _hotkey_action manual_action = get_manual_action(key, modifier_mask);
	if (p->perform_manual_action(p, manual_action))
		return;

	int auto_action = get_auto_action(key, modifier_mask);
	p->perform_auto_action(p, auto_action);
}

static void xprogram_perform_user_action(struct _xprogram *p, int action)
{
	if (p) {};
	
	log_message(DEBUG, _("Execute user action \"%s\""), xconfig->actions->action_command->data[action].string); 
	
	pthread_attr_t action_thread_attr;
	pthread_attr_init(&action_thread_attr);
	pthread_attr_setdetachstate(&action_thread_attr, PTHREAD_CREATE_DETACHED);

	pthread_t action_thread;
	pthread_create(&action_thread, &action_thread_attr,(void *) &system, (void *) xconfig->actions->action_command->data[action].string);
	
	pthread_attr_destroy(&action_thread_attr);
	
}

static void xprogram_perform_auto_action(struct _xprogram *p, int action)
{
	struct _xstring *string = p->string;

	switch (action)
	{
		case KLB_CLEAR:
		{
			p->string->save_and_clear(p->string, p->focus->owner_window);
			return;
		}
		case KLB_DEL_SYM:
		{
			string->del_symbol(string);
			return;
		}
		case KLB_ENTER:
		case KLB_SPACE:
		case KLB_ADD_SYM:
		{

			if ((action == KLB_ENTER) && (xconfig->dont_process_when_press_enter))
			{
				action = KLB_ADD_SYM;
			}
			
			if (p->changed_manual == MANUAL_FLAG_SET)
				p->changed_manual = MANUAL_FLAG_NEED_FLUSH;

			p->last_action = action;
                        
                        char sym = main_window->xkeymap->get_cur_ascii_char(main_window->xkeymap, p->event->event);

                        int modifier = groups[get_cur_lang()] | p->event->get_cur_modifiers(p->event);
			char *symbol = keycode_to_symbol(p->event->event.xkey.keycode, -1, modifier);
                        log_message(DEBUG, _("Pressed key (%s)"), symbol);

			if (action == KLB_ADD_SYM)
			{
				if (p->changed_manual == MANUAL_FLAG_NEED_FLUSH)
					p->changed_manual = MANUAL_FLAG_UNSET;
				
				// Add symbol to internal bufer
				int modifier_mask = groups[get_cur_lang()] | p->event->get_cur_modifiers(p->event);
				p->string->add_symbol(p->string, sym, p->event->event.xkey.keycode, modifier_mask);

			}else{

			// Block events of keyboard (push to event queue)
			set_event_mask(p->focus->owner_window, None);

			// Add symbol to internal bufer
			p->event->event = p->event->default_event;
			int modifier_mask = groups[get_cur_lang()] | p->event->get_cur_modifiers(p->event);
			p->string->add_symbol(p->string, sym, p->event->event.xkey.keycode, modifier_mask);

			// Send Event
			p->event->send_next_event(p->event);
			p->event->default_event.xkey.keycode = 0;
						
			// Unblock keyboard
			set_event_mask(p->focus->owner_window, INPUT_HANDLE_MASK | FOCUS_CHANGE_MASK | EVENT_PRESS_MASK);

			p->changed_manual = MANUAL_FLAG_NEED_FLUSH;
			
			if ((action == KLB_ENTER) && (xconfig->flush_buffer_when_press_enter))
			{
				p->string->save_and_clear(p->string, p->focus->owner_window);
			}
                        
                        }

			return;
		}
	}
}

static int xprogram_perform_manual_action(struct _xprogram *p, enum _hotkey_action action)
{
	switch (action)
	{
		case ACTION_NONE:
			return FALSE;
		case ACTION_CHANGE_MODE:	// User needs to change current work mode
		{
			return TRUE;
		}
		case ACTION_CHANGE_SELECTED:
		case ACTION_CHANGECASE_SELECTED:
		{
			p->selected_mode = action;
			do_selection_notify();
			p->event->default_event.xkey.keycode = 0;
			return TRUE;
		}
		case ACTION_CHANGE_STRING:	// User needs to change current string
		{
			break;
		}
		case ACTION_CHANGE_WORD:	// User needs to cancel last change
		{
			break;
		}
		case ACTION_ENABLE_LAYOUT_0:
		{
			switch_group(0);
			p->event->default_event.xkey.keycode = 0;
			break;
		}
		case ACTION_ENABLE_LAYOUT_1:
		{
			switch_group(1);
			p->event->default_event.xkey.keycode = 0;
			break;
		}
		case ACTION_ENABLE_LAYOUT_2:
		{
			switch_group(2);
			p->event->default_event.xkey.keycode = 0;
			break;
		}
		case ACTION_ENABLE_LAYOUT_3:
		{
			switch_group(3);
			p->event->default_event.xkey.keycode = 0;
			break;
		}
		case ACTION_REPLACE_ABBREVIATION: // User needs to replace acronym
		{
			// Check last word to acronym list
			return FALSE;
		}
	}

	// When CHANGE_STRING or CHANGE_WORD actions occured
	p->changed_manual = MANUAL_FLAG_SET;

	return TRUE;
}

static void xprogram_send_string_silent(struct _xprogram *p, int send_backspaces)
{
	if (p->string->cur_pos == 0)
	{
		log_message(DEBUG, _("No string to change"));
		return;
	}

	log_message(DEBUG, _("Processing string '%s'"), p->string->content);
	
	p->event->send_backspaces(p->event, send_backspaces);		// Delete old string
	p->event->send_string(p->event, p->string);		// Send new string
}





static void xprogram_uninit(struct _xprogram *p)
{
	p->focus->uninit(p->focus);
	p->event->uninit(p->event);
	p->string->uninit(p->string);
	main_window->uninit(main_window);

	free(p);

	log_message(DEBUG, _("Program is freed"));
}

struct _xprogram* xprogram_init(void)
{
	struct _xprogram *p = (struct _xprogram*) malloc(sizeof(struct _xprogram));
	bzero(p, sizeof(struct _xprogram));

	main_window = xwindow_init();

	if (!main_window->create(main_window) || !main_window->init_keymap(main_window))
	{
		free(p);
		return NULL;
	}

	p->modifier_mask		= NO_MODIFIER_MASK;

	p->event			= xevent_init();		// X Event processor
	p->focus			= xfocus_init();		// X Input Focus and Pointer processor
	p->string			= xstring_init();		// Input string buffer

	// Function mapping
	p->uninit			= xprogram_uninit;
	p->update			= xprogram_update;
	p->on_key_action		= xprogram_on_key_action;
	p->process_input		= xprogram_process_input;
	p->perform_auto_action		= xprogram_perform_auto_action;
	p->perform_manual_action	= xprogram_perform_manual_action;
	p->perform_user_action		= xprogram_perform_user_action;
	p->process_selection_notify	= xprogram_process_selection_notify;
	p->send_string_silent		= xprogram_send_string_silent;

	return p;
}
